import sys
import os
import io
import re
import unittest

import tempfile
from logging import StreamHandler
from contextlib import contextmanager
from datetime import datetime

from bodhi.routing import *
from bodhi.routing.segments import *


class RouteTestCase(unittest.TestCase):

    def test_parse_route(self):
        parts = Route.parse('{controller}/{action}/{id}')
        self.assertEqual(len(parts), 3)
        self.assertEqual(len(parts[0].segments), 1)
        self.assert_(isinstance(parts[0].segments[0], DynamicSegment) and parts[0].segments[0].key == 'controller')

    def test_parse_path_route(self):
        parts = Route.parse('static/{*path_info}')
        self.assertEqual(len(parts), 2)
        self.assertEqual(len(parts[0].segments), 1)
        self.assert_(isinstance(parts[0].segments[0], StaticSegment) and parts[0].segments[0].value == 'static')
        self.assert_(isinstance(parts[1].segments[0], PathSegment) and parts[1].segments[0].key == 'path_info')

    def test_parse_route_with_complex_part(self):
        parts = Route.parse('{controller}/{action}/{v1}-{v2}.xml')
        self.assertEqual(len(parts), 3)
        self.assertEqual(len(parts[2].segments), 4)
        self.assert_(isinstance(parts[2].segments[0], DynamicSegment) and parts[2].segments[0].key == 'v1')
        self.assert_(isinstance(parts[2].segments[1], StaticSegment) and parts[2].segments[1].value == '-')
        self.assert_(isinstance(parts[2].segments[2], DynamicSegment) and parts[2].segments[2].key == 'v2')
        self.assert_(isinstance(parts[2].segments[3], StaticSegment) and parts[2].segments[3].value == '.xml')
    
    def test_syntax(self):
        with self.assertRaisesRegexp(ValueError, 'non-optional part:'):
            route = Route('{controller}/{action}/{id}', defaults={'controller': 'blog', 'id': None})

    def test_syntax2(self):
        with self.assertRaisesRegexp(ValueError, 'a route can'):
            route = Route('{controller}/{*action}/{*id}', defaults={'controller': 'blog', 'id': None})
        with self.assertRaisesRegexp(ValueError, 'path_segment'):
            route = Route('{controller}/{*action}/{id}', defaults={'controller': 'blog', 'id': None})

    def test_match(self):
        route = Route('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})
        results = route.match('/blog')
        self.assert_(results['controller'] == 'blog')
        self.assert_(results['action'] == 'index')
        self.assert_(results['id'] == None)
        
    def test_match2(self):
        route = Route('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None})
        results = route.match('', 1)
        self.assert_(results['controller'] == 'home')
        self.assert_(results['action'] == 'index')
        self.assert_(results['id'] == None)
        
    def test_match3(self):
        route = Route('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None}, constraints={'controller': '[a-zA-Z]+'})
        results = route.match('/1/')
        self.assertEqual(results, None)
        
    def test_match4(self):
        route = Route('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})
        results = route.match('/blog/')
        self.assert_(results['controller'] == 'blog')
        self.assert_(results['action'] == 'index')
        self.assert_(results['id'] == None)
        
    def test_match5(self):
        route = Route('{controller}a/{action}/{id}', defaults={'action': 'index', 'id': None})
        results = route.match('/bloga/')
        self.assert_(results['controller'] == 'blog')
        self.assert_(results['action'] == 'index')
        self.assert_(results['id'] == None)
        
    def test_match_0_part(self):
        route = Route('/', defaults={'controller': 'blog', 'action': 'index', 'id': None})
        results = route.match('/')
        self.assert_(results['controller'] == 'blog')
        self.assert_(results['action'] == 'index')
        self.assert_(results['id'] == None)
        
    def test_match_root_dir(self):
        route = Route('{filename}',
                constraints={'filename': r'favicon\.ico|robot\.txt'},
                defaults={'controller': 'public'})
        results = route.match('/favicon.ico')
        self.assertEqual(results['controller'], 'public')
        self.assertEqual(results['filename'], 'favicon.ico')

        results = route.match('/robot.txt')
        self.assertEqual(results['controller'], 'public')
        self.assertEqual(results['filename'], 'robot.txt')

        results = route.match('/favicon.icon')
        self.assertEqual(results, None)

        results = route.match('/favicon-ico')
        self.assertEqual(results, None)

        results = route.match('/robot.txtn')
        self.assertEqual(results, None)

        results = route.match('/arobot.txt')
        self.assertEqual(results, None)

    def test_match_lowercased(self):
        route = Route('{controller}a/{action}/{id}', defaults={'action': 'index', 'id': None})
        results = route.match('/BlogA/')
        self.assertEqual(results['controller'], 'Blog')
        self.assertEqual(results['action'], 'index')

    def test_match_lowercased_mapper(self):
        # route is lowercased, so is the path argument of mapper.match.
        mapper = Mapper()
        mapper.connect('{controller}a/{action}/{id}', defaults={'action': 'index', 'id': None})

        results = mapper.match('/BlogA/')
        self.assertEqual(results['controller'], 'Blog')
        self.assertEqual(results['action'], 'index')

    def test_reverse_not_lowercased(self):
        route = Route('{controller}a/{action}/{id}', defaults={'action': 'index', 'id': None})
        path = route.reverse({'controller': 'Blog', 'action': 'delete', 'id': '100'}, {})
        self.assertEqual(path, 'Bloga/delete/100') # the "B" in "Blog" still capitalized.

    def test_reverse_escaped(self):
        route = Route('{controller} /{action}/{id}', defaults={'action': 'index', 'id': None})
        path = route.reverse({'controller': 'b log', 'action': 'delete', 'id': '100'}, {})
        self.assertEqual(path, 'b%20log%20/delete/100')

    def test_reverse_constraint_on_keys_with_default(self):
        route = Route('{controller} /{action}/{id}', defaults={'action': 'index', 'id': None}, constraints={'id': '\d+'})
        path = route.reverse({'controller': 'b log', 'action': 'delete'}, {})
        self.assertEqual(path, 'b%20log%20/delete')


    def test_reverse2(self):
        route = Route('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})
        path = route.reverse({'controller': 'blog', 'action': 'index', 'id': '100'}, {})
        self.assertEqual(path, 'blog/index/100')
        
    def test_reverse3(self):
        route = Route('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})
        path = route.reverse({'controller': 'blog', 'action': 'index'}, {})
        self.assertEqual(path, 'blog')

    def test_reverse4(self):
        route = Route('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'controller': 'home', 'action': 'index'}, {})
        self.assertEqual(path, '')

    def test_reverse5(self):
        route = Route('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'controller': 'home', 'action': 'index', 'page': '1', 'order_by': ' a'}, {})
        self.assert_(path == '?page=1&order_by=+a' or path == '?order_by=+a&page=1')

    def test_reverse_0_part(self):
        route = Route('', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'controller': 'home', 'action': 'index', 'id': '1' }, {})
        self.assertEqual(path, None)

    def test_reverse_0_part2(self):
        route = Route('', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'controller': 'home', 'action': 'index', 'id': None, 'page': '1' }, {})
        self.assertEqual(path, '?page=1')

    def test_reverse_0_part3(self):
        route = Route('', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'action': 'index', 'page': '1' }, {})
        self.assertEqual(path, None)

    def test_reverse_ref_0(self):
        route = Route('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'controller': 'home', 'page': '1' }, {'controller': 'a', 'action': 'delete'})
        self.assertEqual(path, '?page=1')

    def test_reverse_ref_2(self):
        route = Route('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None})
        path = route.reverse({'page': '1' }, {'controller': 'home', 'action': 'delete', 'a': 'a'})
        self.assertEqual(path, 'home/delete?page=1')

    def test_reverse_ref_3(self):
        route = Route('static/{action}/{id}', defaults={'controller': 'static', 'action': 'index', 'id': None})
        path = route.reverse({'page': '1' }, {'controller': 'home', 'action': 'delete'})
        self.assertEqual(path, None) # ref_value not statisfy required_default_keys 

    def test_reverse_ref_4(self):
        route = Route('static/{action}/{id}', defaults={'controller': 'static', 'action': 'index', 'id': None})
        path = route.reverse({'controller': 'static', 'page': '1' }, {'controller': 'home', 'action': 'delete'})
        self.assertEqual(path, 'static?page=1')

    def test_reverse_requried_defaults(self):
        route = Route('static/{action}/{id}', defaults={'controller': 'static', 'action': 'index', 'id': None})
        path = route.reverse({'page': '1' }, {'controller': 'static', 'action': 'index'})
        self.assertEqual(path, 'static?page=1')


class MapperTestCase(unittest.TestCase):
    def test_match(self):
        mapper = Mapper()
        mapper.connect('{project_id}/{controller}/{action}/{id}', name='project', defaults={'action': 'index', 'id': None}, constraints={'project_id': '\d+'})
        mapper.connect('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})

        results = mapper.match('/blog')
        self.assertEqual(results['controller'], 'blog')
        self.assertEqual(results['action'], 'index')
        self.assertEqual(results['id'], None)

    def test_reverse1(self):
        mapper = Mapper()
        mapper.connect('{project_id}/{controller}/{action}/{id}', name='project', defaults={'action': 'index', 'id': None}, constraints={'project_id': '\d+'})
        mapper.connect('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})

        path = mapper.reverse({'controller': 'blog', 'action': 'index', 'project_id': 100}, {}, route_name="project")
        self.assertEqual(path, '/100/blog')

    def test_reverse2(self):
        mapper = Mapper()
        mapper.connect('{project_id}/{controller}/{action}/{id}', name='project', defaults={'action': 'index', 'id': None}, constraints={'project_id': '\d+'})
        mapper.connect('{controller}/{action}/{id}', defaults={'controller': 'home', 'action': 'index', 'id': None})

        path = mapper.reverse({'controller': 'home', 'action': 'index'}, {})
        self.assertEqual(path, '/')
  
    def test_reverse_qs_in_ref_value_discarded(self):
        mapper = Mapper()
        mapper.connect('{project_id}/{controller}/{action}/{id}', name='project', defaults={'action': 'index', 'id': None}, constraints={'project_id': '\d+'})
        mapper.connect('{controller}/{action}/{id}', defaults={'action': 'index', 'id': None})

        values = {'controller': 'blog', 'action': 'index', 'project_id': 100}
        path = mapper.reverse(values=values, ref_values=dict(values, order='author'), route_name="project")
        self.assertEqual(path, '/100/blog')

